﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

#if NETFX_CORE
using Windows.ApplicationModel.Resources;

#endif

namespace StyleMVVM.Validation.Impl.Fluent
{
	public class FluentDoubleProperty<T> : IDoublePropertyOperator<T>, IFluentPropertyValueProvider<T>
	{
		private enum ModifierType
		{
			Add,
			Subtract,
			Multiply,
			Divide,
			Mod,
			Floor,
			Ceiling
		}

		private readonly IFluentRule<T> fluentRule;
		private ModifierType modifier;
		private double modifierValue;
		private static MethodInfo floorMethod;
		private static MethodInfo ceilingMethod;
		private static MethodInfo addMethod;
		private static MethodInfo subtractMethod;
		private static MethodInfo multiplyMethod;
		private static MethodInfo divideMethod;
		private static MethodInfo modMethod;

		static FluentDoubleProperty()
		{
			floorMethod = typeof(FluentDoubleProperty<object>).GetTypeInfo().GetDeclaredMethod("FloorObject");

			ceilingMethod = typeof(FluentDoubleProperty<object>).GetTypeInfo().GetDeclaredMethod("CeilingObject");

			addMethod = typeof(FluentDoubleProperty<object>).GetTypeInfo().GetDeclaredMethod("AddObject");

			subtractMethod = typeof(FluentDoubleProperty<object>).GetTypeInfo().GetDeclaredMethod("SubtractObject");

			multiplyMethod = typeof(FluentDoubleProperty<object>).GetTypeInfo().GetDeclaredMethod("MultiplyObject");

			divideMethod = typeof(FluentDoubleProperty<object>).GetTypeInfo().GetDeclaredMethod("DivideObject");

			modMethod = typeof(FluentDoubleProperty<object>).GetTypeInfo().GetDeclaredMethod("ModuleObject");
		}

		public FluentDoubleProperty(IFluentRule<T> fluentRule, string propertyName)
		{
			this.fluentRule = fluentRule;

			PropertyName = propertyName;
		}

		public IFluentRule<T> Plus(double x)
		{
			modifier = ModifierType.Add;

			modifierValue = x;

			return fluentRule;
		}

		public IFluentRule<T> Minus(double x)
		{
			modifier = ModifierType.Subtract;

			modifierValue = x;

			return fluentRule;
		}

		public IFluentRule<T> Multiply(double x)
		{
			modifier = ModifierType.Multiply;

			modifierValue = x;

			return fluentRule;
		}

		public IFluentRule<T> Divide(double x)
		{
			modifier = ModifierType.Divide;

			modifierValue = x;

			return fluentRule;
		}

		public IFluentRule<T> Mod(double x)
		{
			modifier = ModifierType.Mod;

			modifierValue = x;

			return fluentRule;
		}

		public IFluentRule<T> Floor()
		{
			modifier = ModifierType.Floor;

			return fluentRule;
		}

		public IFluentRule<T> Ceiling()
		{
			modifier = ModifierType.Ceiling;

			return fluentRule;
		}

		public string PropertyName { get; private set; }

		public Expression GenerateExpression(IFluentExpressionProvider propertyExpressionProvider)
		{
			Expression returnValue = null;

			switch (modifier)
			{
				case ModifierType.Add:
					returnValue =
						Expression.Call(addMethod,
						                propertyExpressionProvider.GetPropertyExpression(PropertyName),
						                Expression.Constant(modifierValue));

					break;
				case ModifierType.Subtract:
					returnValue =
						Expression.Call(subtractMethod,
						                propertyExpressionProvider.GetPropertyExpression(PropertyName),
						                Expression.Constant(modifierValue));

					break;
				case ModifierType.Multiply:
					returnValue =
						Expression.Call(multiplyMethod,
						                propertyExpressionProvider.GetPropertyExpression(PropertyName),
						                Expression.Constant(modifierValue));

					break;
				case ModifierType.Divide:
					returnValue =
						Expression.Call(divideMethod,
						                propertyExpressionProvider.GetPropertyExpression(PropertyName),
						                Expression.Constant(modifierValue));
					break;
				case ModifierType.Mod:
					returnValue =
						Expression.Call(modMethod,
						                propertyExpressionProvider.GetPropertyExpression(PropertyName),
						                Expression.Constant(modifierValue));

					break;
				case ModifierType.Floor:
					returnValue =
						Expression.Call(floorMethod,
						                propertyExpressionProvider.GetPropertyExpression(PropertyName));

					break;
				case ModifierType.Ceiling:
					returnValue =
						Expression.Call(ceilingMethod,
						                propertyExpressionProvider.GetPropertyExpression(PropertyName));

					break;
			}

			return returnValue;
		}

		public string GenerateDisplayString(ResourceLoader loader)
		{
			string returnValue = null;

#if NETFX_CORE
			string formatString;

			switch (modifier)
			{
				case ModifierType.Add:
					formatString = loader.GetString("Plus");

					returnValue = string.Format(formatString, PropertyName, modifierValue);
					break;
				case ModifierType.Subtract:
					formatString = loader.GetString("Minus");

					returnValue = string.Format(formatString, PropertyName, modifierValue);
					break;
				case ModifierType.Multiply:
					formatString = loader.GetString("Times");

					returnValue = string.Format(formatString, PropertyName, modifierValue);
					break;
				case ModifierType.Divide:
					formatString = loader.GetString("DividedBy");

					returnValue = string.Format(formatString, PropertyName, modifierValue);
					break;
				case ModifierType.Mod:
					formatString = loader.GetString("Modules");

					returnValue = string.Format(formatString, PropertyName, modifierValue);
					break;
				case ModifierType.Ceiling:
					formatString = loader.GetString("Ceiling");

					returnValue = string.Format(formatString, PropertyName);
					break;
				case ModifierType.Floor:
					formatString = loader.GetString("Floor");

					returnValue = string.Format(formatString, PropertyName);
					break;
			}
#else
			switch (modifier)
			{
				case ModifierType.Add:
					returnValue = string.Format(FluentResources.Plus, PropertyName, modifierValue);
					break;
				case ModifierType.Subtract:
					returnValue = string.Format(FluentResources.Minus, PropertyName, modifierValue);
					break;
				case ModifierType.Multiply:
					returnValue = string.Format(FluentResources.Times, PropertyName, modifierValue);
					break;
				case ModifierType.Divide:
					returnValue = string.Format(FluentResources.DividedBy, PropertyName, modifierValue);
					break;
				case ModifierType.Mod:
					returnValue = string.Format(FluentResources.Modules, PropertyName, modifierValue);
					break;
				case ModifierType.Ceiling:
					returnValue = string.Format(FluentResources.Ceiling, PropertyName);
					break;
				case ModifierType.Floor:
					returnValue = string.Format(FluentResources.Floor, PropertyName);
					break;
			}
#endif

			return returnValue;
		}

		private static object AddObject(object target, double addValue)
		{
			if (target is double)
			{
				return (double)target + addValue;
			}

			return null;
		}

		private static object SubtractObject(object target, double addValue)
		{
			if (target is double)
			{
				return (double)target - addValue;
			}

			return null;
		}

		private static object MultiplyObject(object target, double addValue)
		{
			if (target is double)
			{
				return (double)target * addValue;
			}

			return null;
		}

		private static object DivideObject(object target, double addValue)
		{
			if (target is double)
			{
				return (double)target / addValue;
			}

			return null;
		}

		private static object ModuleObject(object target, double addValue)
		{
			if (target is double)
			{
				return (double)target % addValue;
			}

			return null;
		}

		private static double FloorObject(object targetValue)
		{
			if (targetValue is double)
			{
				double targetDouble = (double)targetValue;

				return Math.Floor(targetDouble);
			}

			return 0;
		}

		private static double CeilingObject(object targetValue)
		{
			if (targetValue is double)
			{
				double targetDouble = (double)targetValue;

				return Math.Ceiling(targetDouble);
			}

			return 0;
		}
	}
}